/**
 * dbms-sjtu
 * UpdatePlanner.java create @ ����9:45:19
 * @author zhangweipeng
 */
package dbms.planner;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import dbms.Constants;
import dbms.Debug;
import dbms.absyn.*;
import dbms.metadata.TableManager;
import dbms.record.FieldInfo;
import dbms.record.Record;
import dbms.record.RecordPage;
import dbms.record.Schema;
import dbms.server.DbmsServer;

/**
 * @author zhangweipeng.pt
 *
 */
public class UpdatePlanner {
	
	/**
	 * execute a insert SQL statement, just like
	 * INSERT INTO student(...) VALUES(...)
	 */
	public static void executeInsert(InsertExp insertData){
		//step 1 : get the table info by table name
		String tableName = insertData.insert_stmt.table.name;
		//TableInfo newTable = TableManager.getTableInfo(tableName);
		RecordPage recordPage = TableManager.getTableRecordPage(tableName);
		if(recordPage == null) return;
		//step 2 : get value list by field rank , now only support all-field insert style
		List<Object> dataList = new ArrayList<Object>();
		if(recordPage != null){
			//Map<String, Object> recordData = new HashMap<String, Object>();
			AttrList attrList = insertData.insert_stmt.attr_list;
			ValueList valueList = insertData.value_stmt.value_list;
			if(attrList != null){
				Debug.Error("Now we do not support this insert style.");
				return;
			} else {
				String fieldString = null;
				int count = 0, type;
				while(valueList != null){
					for(String field : recordPage.table.getTableSchema().getFieldRank().keySet()){
						if(recordPage.table.getTableSchema().getFieldRank().get(field) == count){
							fieldString = field; 
							break;
						}
					}
					type = recordPage.table.getTableSchema().getInfo().get(fieldString)
							.getType();
					Object object = null;
					if (type == Constants.INTEGER) {
						object = valueList.value.number;
						dataList.add(object);
					} else if (type == Constants.DOUBLE) {
						object = valueList.value.doubleNum;
						dataList.add(object);
					} else if (type == Constants.VARCHAR) {
						if (valueList.value.str.length() > recordPage.table
								.getTableSchema().getInfo().get(fieldString)
								.getLength()) {
							Debug.Error("VARCHAR your length is too large.");
							return;
						}
						object = valueList.value.str;
						dataList.add(object);
					} else if (type == Constants.CHAR) {
						if (valueList.value.str.length() != recordPage.table.getTableSchema().getInfo().get(fieldString).getLength()) {
							Debug.Error("CHAR your length is not equal with the schema length.");
							return;
						}
						object = valueList.value.str;
						dataList.add(object);
					} else if(type == Constants.DECIMAL){
						int len = recordPage.table.getTableSchema().getInfo().get(fieldString).getLength();
						int dotDefore = len / (1 << 16);
						int dotAfter = len % (1 << 16);
						object = valueList.value.doubleNum;
						if(object instanceof Float){
							double fuck = (((Float) object) - ((Float) object).intValue()) * Math.pow(10, dotAfter);
							if(((Float) object).intValue() > Math.pow(10, dotDefore) || 
									Math.abs(fuck - Math.rint(fuck)) > Math.pow(10, -3)){
								Debug.Error("Decimal(" + dotDefore + "," + dotAfter +"), your data " + object + " length is wrong.");
								return;
							}
						}
						
						
						dataList.add(object);
					} else {
						//FIXME
					}

					if (recordPage.table.getTableSchema().primaryKeyField != null
							&& recordPage.table.getTableSchema().primaryKeyField
									.equals(fieldString)) {
						if (recordPage.table.primaryKeyIndex.contains(object)) {
							Debug.Error("Your data is conflict with the exist record data. Primary Key.");
							return;
						} else {
							recordPage.table.primaryKeyIndex.add(object);
						}
					}
					
					count++;
					valueList = valueList.next;
				}
				if(count != recordPage.table.getTableSchema().getInfo().size()){
					Debug.Error("Now we do not support this insert style. All field data need.");
					return;
				}
			}
			
		// step 3 : use RecordPage to insert data
			recordPage.insert(new Record(0, dataList));
		}
	}
	
	/**
	 * execute a delete SQL statement, just like
	 * DELETE FROM student where column=value
	 */
	public static void executeDelete(DeleteExp deleteData){
		//step 1: get table's buffer by table name
		String tableName = deleteData.delete_stmt.table.name;
		

		RecordPage recordPage = TableManager.getTableRecordPage(tableName);
		if(recordPage == null) return;
		
		List<String> fieldList = new ArrayList<String>();
		for(String field : recordPage.table.getTableSchema().getFieldRank().keySet()){
			fieldList.add(field);
		}
		
		//step 2 : check the field and get all data
		Map<Integer, List<Object>> data = recordPage.select(fieldList);
		
		//step 3: analysis condition list
		ConditionList conditionList = deleteData.where_stmt.condition_list;
		
		for(Integer integer : data.keySet()){
			if(conditionList.satisfy(fieldList, data.get(integer))){
				recordPage.delete(integer);
			}
		}
	}
	
	/**
	 * execute a update SQL statement, just like
	 * UPDATE Person SET FirstName = 'Fred' WHERE LastName = 'Wilson' 
	 */
	public static void executeUpdate(UpdateExp updateData){
		//step 1: get table's buffer by table name
		String tableName = updateData.update_stmt.table.name;
		
		RecordPage recordPage = TableManager.getTableRecordPage(tableName);
		if(recordPage == null) return;
		
		//step 2: get data map from set list
		Map<String, Object> dataMap = new HashMap<String, Object>();
		SetList setList = updateData.set_stmt.set_list;
		while(setList != null){
			Set set = setList.set;
			if(set.value.str == null){
				dataMap.put(set.attr.attr.name, set.value.number);
			} else {
				dataMap.put(set.attr.attr.name, set.value.str);
			}
			setList = setList.next;
		}
		
		//step 3 : check the field and get all data
		List<String> fieldList = new ArrayList<String>();
		for(String field : recordPage.table.getTableSchema().getFieldRank().keySet()){
			fieldList.add(field);
		}
		
		Map<Integer, List<Object>> data = recordPage.select(fieldList);
		
		//step 3: analysis condition list, and update 
		ConditionList conditionList = updateData.where_stmt.condition_list;
		
		for(Integer integer : data.keySet()){
			if(conditionList.satisfy(fieldList, data.get(integer))){
				recordPage.update(integer, dataMap);
			}
		}
	}
	
	/**
	 * CREATE TABLE student(id INT, name CHAR(8));
	 */
	public static  void executeCreateTable(CreateTabClause createTableData){
		String tableName = createTableData.table.name;
		if(DbmsServer.masterPage.tableMap.containsKey(tableName)){
			Debug.Error("This table name is already exsit.");
			return;
		}
		Schema schema = new Schema();
		TableContentList tableContentList = createTableData.table_content_list;
		TableContent tableContent = null;
		int keyCount = 0, key = 0;
		while(tableContentList != null){
			tableContent = tableContentList.table_content;
			if(tableContent.constraint != null){
				if(tableContent.constraint.constraint == "PRIMARY KEY"){
					keyCount++;
					key = 2;
					schema.primaryKeyField = tableContent.attr.attr.name;
				} else if(tableContent.constraint.constraint == "NOT NULL"){
					key = 1;
				} else {
					//FIXME
				}
			}
			if(tableContent.data_type.type.equals("INT")){
				schema.addField(tableContent.attr.attr.name, new FieldInfo(Constants.INTEGER, Constants.INT_SIZE, key));
			} else if(tableContent.data_type.type.equals("VARCHAR")){
				schema.addField(tableContent.attr.attr.name, new FieldInfo(Constants.VARCHAR, tableContent.data_type.length, key));
			} else if(tableContent.data_type.type.equals("CHAR")){
				schema.addField(tableContent.attr.attr.name, new FieldInfo(Constants.CHAR, tableContent.data_type.length, key));
			} else if(tableContent.data_type.type.equals("DOUBLE")){
				schema.addField(tableContent.attr.attr.name, new FieldInfo(Constants.DOUBLE, Constants.DOUBLE_SIZE, key));
			} else if(tableContent.data_type.type.equals("DECIMAL")){
				schema.addField(tableContent.attr.attr.name, new FieldInfo(Constants.DECIMAL, tableContent.data_type.length, key));
			} else {
				Debug.P("Type error   " + tableContent.data_type.type);
				return;
			}	
			key = 0;
			tableContentList = tableContentList.next;
		}
		if(keyCount >= 2){
			Debug.P("Too many PRIMARY KEY.");
			return;
		}
		TableManager.createTable(tableName, schema);
	}
	
	/**
	 * DROP TABLE table;
	 * @param dropTabClause
	 */
	public static void executeDropTable(DropTabClause dropTabClause){
		String tableName = dropTabClause.table.name;
		if(!DbmsServer.masterPage.tableMap.containsKey(tableName)){
			Debug.Error("This table " + tableName + " does not exist.");
		} else {
			TableManager.DropTable(tableName);
		}
	}
	
	/**
	 * CREATE INDEX index_name ON table_name (column_name)
	 */
	public static void executeCreateIndex(IndexExp indexData){
		//TODO
	}
	
	/**
	 * CREATE VIEW view_name AS
	 * SELECT column_name(s)
	 * FROM table_name
	 * WHERE condition
	 */
	public static void executeCreateView(String sql, ViewExp viewData){
		//step 1: get the view name and related SelectExp object
		String viewName = viewData.view_stmt.view.name;
		//SelectExp selectExp = new SelectExp(viewData.pos, viewData.select_stmt, viewData.from_stmt, viewData.where_stmt);
		DbmsServer.masterPage.insert(viewName, sql);
	}
	
	/**
	 * DROP VIEW viewName;
	 * @param viewName
	 */
	public static void executeDropView(DropViewClause dropViewClause){
		String viewName = dropViewClause.view.name;
		if(DbmsServer.masterPage.viewMap.containsKey(viewName)){
			DbmsServer.masterPage.delete(DbmsServer.masterPage.viewMap.get(viewName).rowID);
		} else {
			Debug.Error("This view " + viewName + " does not exist.");
		}
		
	}
	
}
